﻿using FySystem.Net.SimSocket.Protocals;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FySystem.Net.SimSocket
{
    public class SimClient : IDisposable
    {
        //private bool _receiveOnce = false;
        private ushort _version = 1;                    //当前版本号
        private HandlerBase _handerBase;
        private Task _taskPing;                         //定时发送ping
        private Task _taskCheck;                        //校验是否断线
        private bool _isConnecting = false;             //是否正在连接服务器

        /// <summary>
        /// 客户端信息
        /// </summary>
        public SimSocketInfo SimSocketInfo { get; set; } = new SimSocketInfo();

        /// <summary>
        /// 完成连接后的回调
        /// </summary>
        public Action<SimClient> OnConnect { get; set; }

        /// <summary>
        /// 数据发送完成后回调
        /// </summary>
        public Action<SimClient> OnSend { get; set; }

        /// <summary>
        /// 收到数据后的回调
        /// </summary>
        public Action<SimClient, FrameInfo> OnReceive { get; set; }

        /// <summary>
        /// 出错回调
        /// </summary>
        public Action<Exception,SimClient> OnException { get; set; }

        /// <summary>
        /// 离线回调
        /// </summary>
        public Action<SimClient> OnDisConnect { get; set; }

        /// <summary>
        /// 判断出离线后是否自动重新连接服务端
        /// </summary>
        public bool IsAutoReconnect { get; set; } = true;

        /// <summary>
        /// 当前是否处于连接状态
        /// </summary>
        public bool IsConnect { get; set; } = false;

        /// <summary>
        /// 检测连接时间（单位s）
        /// </summary>
        public int PingTime { get; } = 30;

        /// <summary>
        /// 连接名称，由用户自己设置，用于标记连接用途
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 自定义数据
        /// </summary>
        public object Tag { get; set; }

        /// <summary>
        /// 数据连接信息
        /// </summary>
        internal SimStateObjectBase SimStateObject { get; set; } = new SimStateObjectBase();

        /// <summary>
        /// 内部使用的构造函数
        /// </summary>
        internal SimClient()
        {
            _handerBase = HandlerManager.GetHandlerByVersion(_version);
        }

        public SimClient(IPAddress remoteIp, int remotePort)
        {
            try
            {
                _handerBase = HandlerManager.GetHandlerByVersion(_version);
                SimSocketInfo = new SimSocketInfo();
                SimSocketInfo.ID = HandlerBase.ConvertGUID(SimSocketInfo.guid.ToByteArray());
                SimSocketInfo.RemoteEndPoint = new IPEndPoint(remoteIp, remotePort);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 连接远端
        /// </summary>
        public void Connect()
        {
            try
            {
                //如果是重连，先释放连接
                try
                {
                    if (SimStateObject.workSocket != null)
                        SimStateObject.workSocket.Dispose();
                }
                catch { }

                SimStateObject.workSocket = new Socket(SimSocketInfo.RemoteEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                //连接远端
                SimStateObject.workSocket.Connect(SimSocketInfo.RemoteEndPoint);

                //发送握手包
                byte[] helloPack = _handerBase.GenHelloCSPackage(SimSocketInfo.guid.ToByteArray(), _version);
                SimStateObject.workSocket.Send(helloPack);

                //等待握手返回
                byte[] resPack = new byte[1024];
                SimStateObject.workSocket.ReceiveTimeout = 15000;
                int bytesRead = SimStateObject.workSocket.Receive(resPack);

                if (bytesRead > 0)
                {
                    bool validate = _handerBase.ValidateHelloReply(ref resPack, bytesRead);
                    if (validate == false)
                        throw new Exception("connection failure~");

                    IsConnect = true;
                    _isConnecting = false;

                    //建立线程，定时发送心跳包
                    if (_taskPing == null)
                    {
                        _taskPing = new Task(new Action(() =>
                          {
                              PingTask();
                          }));
                        _taskPing.Start();
                    }

                    //建立线程，定时校验是否离线
                    if (_taskCheck == null)
                    {
                        _taskCheck = new Task(new Action(() =>
                          {
                              CheckConnection();
                          }));
                        _taskCheck.Start();
                    }

                    //回调
                    OnConnect?.Invoke(this);

                    SimStateObject.workSocket.BeginReceive(SimStateObject.buffer, 0, SimStateObjectBase.BufferSize, SocketFlags.None, MyReceiveCallback, SimStateObject);
                }
            }
            catch (Exception ex)
            {
                OnException?.Invoke(ex, this);
                if (IsAutoReconnect)
                {
                    Thread.Sleep(10000);
                    Task.Run(Connect);
                }
            }
        }

        /// <summary>
        /// 数据接收回调
        /// </summary>
        /// <param name="ar"></param>
        private void MyReceiveCallback(IAsyncResult ar)
        {
            try
            {
                SimStateObjectBase stateObjectBase = (SimStateObjectBase)ar.AsyncState;
                stateObjectBase.lastReceivedTime = DateTime.Now;
                int bytes = SimStateObject.workSocket.EndReceive(ar);

                List<FrameInfo> frames = null;

                if (bytes > 0)
                {
                    frames = _handerBase.DepackFrame(stateObjectBase, bytes);

                    if (frames != null)
                    {
                        foreach (FrameInfo item in frames)
                        {
                            if (item.PackType == HandlerBase.EPackTypes.Normal)
                                OnReceive?.Invoke(this, item);
                            else if (item.PackType == HandlerBase.EPackTypes.Pong)
                                this.IsConnect = true;
                        }
                    }

                    SimStateObject.workSocket.BeginReceive(SimStateObject.buffer, 0, SimStateObjectBase.BufferSize, SocketFlags.None, MyReceiveCallback, SimStateObject);
                }
            }
            catch (Exception ex)
            {
                OnException?.Invoke(ex, this);
            }
        }

        /// <summary>
        /// 线程方法，定时检测连接
        /// </summary>
        private void PingTask()
        {
            while (true)
            {
                Thread.Sleep(1000);

                if ((DateTime.Now - SimStateObject.lastReceivedTime).TotalSeconds >= PingTime)
                    SendPing();
            }
        }

        /// <summary>
        /// 线程方法，定时检测连接是否中断
        /// </summary>
        private void CheckConnection()
        {
            while (true)
            {
                Thread.Sleep(1000);
                try
                {
                    if ((DateTime.Now - SimStateObject.lastReceivedTime).TotalSeconds > PingTime + 20)
                        IsConnect = false;

                    if (IsConnect == false && _isConnecting == false)
                    {
                        _isConnecting = true;
                        SimStateObject.lastReceivedTime = DateTime.Now;
                        OnDisConnect?.Invoke(this);
                        if (IsAutoReconnect)
                            Connect();                      //重连
                    }
                }
                catch { }
            }
        }

        /// <summary>
        /// 发送ping
        /// </summary>
        public void SendPing()
        {
            try
            {
                byte[] data = _handerBase.GenPingPackage();
                SimStateObject.workSocket.Send(data);
            }
            catch (Exception)
            {
                IsConnect = false;
            }
        }

        /// <summary>
        /// 发送pong
        /// </summary>
        public void SendPong()
        {
            try
            {
                byte[] data = _handerBase.GenPongPackage();
                SimStateObject.workSocket.Send(data);
            }
            catch (Exception)
            {
                IsConnect = false;
                throw;
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="bytes"></param>
        public void Send(byte[] bytes, bool isFinish=true, bool isSync=false)
        {
            try
            {
                byte[] pack = _handerBase.PackFrame(new PackInfo()
                {
                    Data = bytes,
                    OpCode = 0,
                    IsFinish = isFinish
                });
                if (isSync)
                    SimStateObject.workSocket.Send(pack);
                else
                    SimStateObject.workSocket.BeginSend(pack, 0, pack.Length, SocketFlags.None, null, null);
                
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 发送普通包
        /// </summary>
        /// <param name="bytes"></param>
        public void Send(ref byte[] bytes, bool isSync = false)
        {
            try
            {
                byte[] pack = _handerBase.PackFrame(ref bytes);
                if (isSync)
                    SimStateObject.workSocket.Send(pack);
                else
                    SimStateObject.workSocket.BeginSend(pack, 0, pack.Length, SocketFlags.None, null, null);
            }
            catch (Exception)
            {
                throw;
            }
        }

        public void Send(List<byte> data, bool isSync=false)
        {
            try
            {
                byte[] pack = _handerBase.PackFrame(data);
                if (isSync)
                    SimStateObject.workSocket.Send(pack);
                else
                    SimStateObject.workSocket.BeginSend(pack, 0, pack.Length, SocketFlags.None, null, null);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <param name="isFinish"></param>
        public void Send(byte[] bytes, int offset, int length,bool isFinish = true,bool isAsync=false)
        {
            try
            {
                byte[] ndata = new byte[length];
                int endIndex = offset + length;
                for (int i = offset; i < endIndex; i++)
                    ndata[i - offset] = bytes[i];

                Send(ndata, isFinish, isAsync);
            }
            catch (Exception)
            {
                throw;
            }
        }

        public void Dispose()
        {
            try
            {
                if (_taskPing != null)
                    _taskPing.Dispose();
                if (_taskCheck != null)
                    _taskCheck.Dispose();
                if (SimStateObject.workSocket != null)
                    SimStateObject.workSocket.Dispose();
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}
